//
//	File Name   :	OpenGLRenderer.cpp
//	Description :	OpenGLRenderer implementation file.
//	Author		  :	William McVicar
//  Mail		    :	mcvicar.william@gmail.com
//

//  Precompiled Header
#include "CommonDefines.h"

#ifdef OPENGL

//  Library Includes
#pragma comment( lib, "opengl32.lib" )
#pragma comment( lib, "glu32.lib" )

//  Local Includes
#include <gl\GL.h>
#include <gl\GLU.h>
#include "OpenGLExtensions.h"

#include "OpenGLRendererData.h"

#include "Camera.h"

#include "IndexBuffer.h"
#include "VertexBuffer.h"
#include "VertexFormat.h"

#include "Mesh.h"
#include "TextureManager.h"
#include "TextureBase.h"
#include "Texture2D.h"
#include "Image.h"
#include "loaders/ddsloader.h"
#include "loaders/bmpLoader.h"

#include "Vector.h"

#include "ProteinFramework.h"


//  This includes
#include "OpenGLRenderer.h"

//	Macros

//  Static Variables

//  Static Function Prototypes

//  Implementation

namespace Protein
{

static std::string glErrorToString(GLenum glError)
{
    switch (glError)
    {
        case GL_NO_ERROR:                           return "GL_NO_ERROR";
        case GL_INVALID_ENUM:                       return "GL_INVALID_ENUM";
        case GL_INVALID_VALUE:                      return "GL_INVALID_VALUE";
        case GL_INVALID_OPERATION:                  return "GL_INVALID_OPERATION";
        case GL_OUT_OF_MEMORY:                      return "GL_OUT_OF_MEMORY";

#ifdef GL_STACK_OVERFLOW
        case GL_STACK_OVERFLOW:                     return "GL_STACK_OVERFLOW";
        case GL_STACK_UNDERFLOW:                    return "GL_STACK_UNDERFLOW";
#endif

#ifdef GL_INVALID_FRAMEBUFFER_OPERATION_EXT
        case GL_INVALID_FRAMEBUFFER_OPERATION_EXT:  return "GL_INVALID_FRAMEBUFFER_OPERATION_EXT";
#endif

		default:                                    return "UNKNOWN_ERROR";
    }
}

OpenGLRenderer::OpenGLRenderer()
: m_iWindowWidth( 640 )
, m_iWindowHeight( 480 )
{

}

OpenGLRenderer::~OpenGLRenderer()
{
	delete m_pMesh;

	m_pMesh	   = NULL;
	m_pTexture = NULL;

	ProteinFramework::textureManager().free( "Ultron_Diffuse.dds" );

  delete m_rendererData;
}

HRESULT OpenGLRenderer::Initialise( HWND _hWnd ) 
{
	HDC deviceContext = GetDC( _hWnd );
	if( !deviceContext )
		return E_FAIL;

	PIXELFORMATDESCRIPTOR pfd;
	memset( &pfd, 0, sizeof( PIXELFORMATDESCRIPTOR ) );
	pfd.nVersion   = 1;
	pfd.nSize	   = sizeof( PIXELFORMATDESCRIPTOR );
	pfd.dwFlags	   = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
	pfd.iPixelType = PFD_TYPE_RGBA;
	pfd.cColorBits = 32;
	pfd.cDepthBits = 32;
	pfd.iLayerType = PFD_MAIN_PLANE;

	int iPixelFormat = ChoosePixelFormat( deviceContext, &pfd );
	if( iPixelFormat == 0 )
		return E_FAIL;

	if( SetPixelFormat( deviceContext, iPixelFormat, &pfd ) == 0 )
		return E_FAIL;

	HGLRC renderingContext = wglCreateContext( deviceContext );
	wglMakeCurrent( deviceContext, renderingContext );
	ShowWindow( _hWnd, SW_SHOW );
	SetForegroundWindow( _hWnd );
	SetFocus( _hWnd );
	Resize( m_iWindowWidth, m_iWindowHeight ); 

	/*i32 glVersion[2] = { -1, -1 };
	glGetIntegerv( GL_MAJOR_VERSION, &glVersion[0] );
	glGetIntegerv( GL_MINOR_VERSION, &glVersion[1] );*/

	std::string strRawExtensions = reinterpret_cast< const char* >( glGetString( GL_EXTENSIONS ) );
	u32 uiLength = strRawExtensions.length();
	std::vector< std::string > extensions;
	i32 iStartStringIndex = 0;
	for( u32 i = 0; i < uiLength; ++i )
	{
		if( strRawExtensions[i] == ' ' )
		{
			std::string str = strRawExtensions.substr( iStartStringIndex, i - iStartStringIndex );
			extensions.push_back( str );
			iStartStringIndex = i+1;
		}
	}

#ifdef PROTEIN_PLATFORM_WINDOWS
	if( OpenGLExtensions::wglGetExtensionsStringARB )
	{
		strRawExtensions = OpenGLExtensions::wglGetExtensionsStringARB( deviceContext );
		const GLenum glError = glGetError(); 
		if( glError )
			__asm{ int 3 }
	}
#endif

	OpenGLExtensions::MapExtensions();

  m_rendererData                      = new RendererData();
  m_rendererData->m_hWnd              = _hWnd;
  m_rendererData->m_pDeviceContext    = deviceContext;
  m_rendererData->m_pRenderingContext = renderingContext;

	bool b = Stuff();

	glEnable( GL_TEXTURE_2D );
	glShadeModel( GL_SMOOTH );
	glClearColor( 0.4f, 0.6f, 0.9f, 0.0f );
	glClearDepth(1.0f);
	glEnable( GL_DEPTH_TEST );
	glDepthFunc( GL_LEQUAL );
	glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );

	return S_OK;
}

void OpenGLRenderer::Render()
{
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );

  glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();

  Matrix4 view = m_pActiveCamera->viewMatrix().transpose(); //Row Major in OpenGL

  glMultMatrixf( view.m_data );
	int i  = checkGLErrors();
	PROTEIN_UNUSED(i);

  /*m_pTexture->Enable();

	m_pMesh->vertexBuffer()->Enable();
	m_pMesh->vertexFormat()->Enable();
	m_pMesh->indexBuffer()->Enable();*/

	//glDrawElements( GL_TRIANGLES, m_pMesh->GetNumIndices(), GL_UNSIGNED_INT, BUFFER_OFFSET(0) );		

  SwapBuffers( m_rendererData->m_pDeviceContext );
}

void OpenGLRenderer::Resize( i32 _iWindowWidth, i32 _iWindowHeight )
{
	m_iWindowWidth  = _iWindowWidth;
	m_iWindowHeight = _iWindowHeight;

	glViewport( 0, 0, m_iWindowWidth, m_iWindowHeight );

	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();
	
	gluPerspective( 45.0f, (GLfloat)m_iWindowWidth/(GLfloat)m_iWindowHeight, 1.0f, 1000.0f );

	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();

	int i  = checkGLErrors();
	PROTEIN_UNUSED(i);
}

GLenum OpenGLRenderer::getGLDataFormat( ePixelFormat pixelFormat )
{
    switch( pixelFormat )
    {
        case PF_RGB8:               return GL_RGB;
        case PF_RGB565:             return GL_RGB;
        case PF_RGBA5551:           return GL_RGBA;
        case PF_ARGB1555:           return GL_BGRA;
        case PF_RGBA4444:           return GL_RGBA;
        case PF_ARGB4444:           return GL_BGRA;
        case PF_RGBA8:              return GL_RGBA;
        case PF_BGR8:               return GL_BGR;
        case PF_BGRA8:              return GL_BGRA;
        case PF_ABGR8:              return GL_ABGR_EXT;
        case PF_DXT1:               return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
        case PF_DXT3:               return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
        case PF_DXT5:               return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;

        default:                    return 0;
	}
}

GLenum OpenGLRenderer::getGLInternalFormat( ePixelFormat pixelFormat )
{
	switch( pixelFormat )
	{
		case PF_RGB8:               return GL_RGB8;
		case PF_BGR8:               return GL_RGB8;
		case PF_RGBA8:              return GL_RGBA8;
		case PF_BGRA8:              return GL_RGBA8;
		case PF_ABGR8:              return GL_RGBA8;
		case PF_RGB565:             return GL_RGB;
		case PF_RGBA5551:           return GL_RGBA;
		case PF_ARGB1555:           return GL_RGBA;
		case PF_RGBA4444:           return GL_RGBA4;
		case PF_ARGB4444:           return GL_RGBA4;
		case PF_DXT1:               return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
		case PF_DXT3:               return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
		case PF_DXT5:               return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;

		default:                    return 0;
	};
}

GLenum OpenGLRenderer::getGLDataType( ePixelFormat pixelFormat )
{
    switch (pixelFormat)
    {
        case PF_RGB8:               return GL_UNSIGNED_BYTE;
        case PF_RGBA8:              return GL_UNSIGNED_BYTE;
        case PF_BGR8:               return GL_UNSIGNED_BYTE;
        case PF_BGRA8:              return GL_UNSIGNED_BYTE;
        case PF_ABGR8:              return GL_UNSIGNED_BYTE;
        case PF_RGB565:             return GL_UNSIGNED_SHORT_5_6_5;
        case PF_RGBA5551:           return GL_UNSIGNED_SHORT_5_5_5_1;
        case PF_ARGB1555:           return GL_UNSIGNED_SHORT_1_5_5_5_REV;
        case PF_RGBA4444:           return GL_UNSIGNED_SHORT_4_4_4_4;
        case PF_ARGB4444:           return GL_UNSIGNED_SHORT_4_4_4_4_REV;
        case PF_DXT1:               return 0;
        case PF_DXT3:               return 0;
        case PF_DXT5:               return 0;
        default:                    return 0;
    };
}

int OpenGLRenderer::checkGLErrors()
{
	int errCount = 0;
	for(GLenum currError = glGetError(); currError != GL_NO_ERROR; currError = glGetError())
	{
		std::string error = glErrorToString( currError );
		++errCount;
	}
 
	return errCount;
}



bool OpenGLRenderer::Stuff()
{
	//m_pMesh = new Mesh();
	////m_pMesh->Load3DS( "teapot.3ds" );
	//m_pMesh->LoadFBX("ultron_model.fbx");
	////m_pMesh->Load("cubefbx");

	m_pTexture = ProteinFramework::textureManager().create("Ultron_Diffuse.tga");
	ProteinFramework::textureManager().create("Future.dds");
	ProteinFramework::textureManager().create("green.bmp");
	ProteinFramework::textureManager().create("lena.dds");

	
	//TextureBase base("green.bmp");
	//TextureBase base( "Ultron_Diffuse.dds" );
	//base.Load();
	//m_pTexture = new Texture2D( &base );

	return true;
}

}

#endif//OPENGL